#include "Matrix.h"

// constructor
Matrix::Matrix()
{
	Reset();
};

// reset matrix
void Matrix::Reset()
{
	for(int i = 0; i < 16; i++){
		if(!(i % 5)) value[i] = 1;
		else value[i] = 0;
	}
};

// display matrix data to console
void Matrix::Print()
{
	for(int i = 0; i < 4; i++){		
		for(int j = 0; j < 4; j++){
			cout.width(10);
			cout << value[i * 4 + j];
		}
		cout << endl;
	}
};

// multiply a matrix
void Matrix::Multiply(const Matrix &a)
{
	float newValue[16];
	float tmp = 0;

	for(int i = 0; i < 4; i++){
		for(int j = 0; j < 4; j++){
			tmp = 0;
			
			for(int k = 0; k < 4; k++){
				tmp += value[i * 4 + k] * a.value[k * 4 + j];
			}

			newValue[i * 4 + j] = tmp;
		}
	}

	for(int i = 0; i < 16; i++){
		value[i] = newValue[i];
	}
};

// add a matrix
void Matrix::Add(const Matrix &a)
{
	for(int i = 0; i < 16; i++){
		value[i] += a.value[i];
	}
};

// rotate a transform matrix arround x, alpha in degree
void Matrix::RotateX(float alpha)
{
    alpha = alpha * PI_NUMBER / 180;

    float cosAlpha = cosf(alpha);
    float sinAlpha = sinf(alpha);

    Matrix a;
    a.value[5] = cosAlpha;
    a.value[6] = sinAlpha;
    a.value[9] = -sinAlpha;
    a.value[10] = cosAlpha;

    Multiply(a);
};

// rotate a transform matrix arround y, alpha in degree
void Matrix::RotateY(float alpha)
{
    alpha = alpha * PI_NUMBER / 180;

    float cosAlpha = cosf(alpha);
    float sinAlpha = sinf(alpha);

    Matrix a;
    a.value[0] = cosAlpha;
    a.value[2] = -sinAlpha;
    a.value[8] = sinAlpha;
    a.value[10] = cosAlpha;

    Multiply(a);
};

// rotate a transform matrix arround z, alpha in degree
void Matrix::RotateZ(float alpha)
{
    alpha = alpha * PI_NUMBER / 180;

    float cosAlpha = cosf(alpha);
    float sinAlpha = sinf(alpha);

    Matrix a;
    a.value[0] = cosAlpha;
    a.value[1] = sinAlpha;
    a.value[4] = -sinAlpha;
    a.value[5] = cosAlpha;

    Multiply(a);    
};

// translate a transform matrix
void Matrix::Translate(float x, float y, float z)
{
    Matrix a;
    a.value[12] = x;
    a.value[13] = y;
    a.value[14] = z;

    Multiply(a);
};

// scale a transform matrix 
void Matrix::Scale(float x, float y, float z)
{
    Matrix a;
    a.value[0] = x;
    a.value[5] = y;
    a.value[10] = z;

    Multiply(a);
};

// load OrthoGraphics to this matrix
void Matrix::LoadOrthoGraphics(float left, float right, float bottom, float top, float near, float far)
{
    float r_l = right - left;
    float t_b = top - bottom;
    float f_n = far - near;
    float tx = - (right + left) / (right - left);
    float ty = - (top + bottom) / (top - bottom);
    float tz = - (far + near) / (far - near);

    memset(value, 0, sizeof(value));
    
    value[0] = 2.0f / r_l;
    value[5] = 2.0f / t_b;
    value[10] = -2.0f / f_n;
    value[12] = tx;
    value[13] = ty;
    value[14] = tz;
    value[15] = 1.0f;

};
  
// set Perpertive
void Matrix::LoadPerperstive(float fovy, float aspect, float zNear, float zFar)
{
    memset(value, 0, sizeof(value));

    fovy = fovy * PI_NUMBER / 180;
    float f = 1.0f / tanf(fovy / 2.0f);
    value[0] = f / aspect;

    value[5] = f;
    value[10] = (zFar + zNear) / (zNear - zFar);    
    value[11] = -1.0f;
    value[14] = 2 * zFar * zNear /  (zNear - zFar);

};

// multiply equal operator
Matrix &Matrix::operator*=(const Matrix &matrix)
{
    Multiply(matrix);
    return *this;
}

// multiply operator
Matrix Matrix::operator*(const Matrix &matrix)
{
    Matrix result(*this);
    return result *= matrix;
}

// plus operator
Matrix Matrix::operator+(const Matrix &matrix)
{
    Matrix result(*this);
    result.Add(matrix);
    return result;
}